﻿namespace TestTool.Tests.Common.Discovery
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Net;
    using System.Net.Sockets;
    using System.Runtime.CompilerServices;
    using System.Text;

    public class DiscoverySocket : IDisposable
    {
        private IPEndPoint _endpoint;

        public event EventHandler<DiscoverySocketEventArgs> MessageReceived;

        public DiscoverySocket(IPEndPoint endpoint)
        {
            this._endpoint = new IPEndPoint(endpoint.Address, endpoint.Port);
            Trace.WriteLine(string.Format("Socket {0} C-tor", this.GetHashCode()));
            Trace.Flush();
            if ((OSInfo.MajorVersion == 5) && (OSInfo.MinorVersion == 1))
            {
                this.Socket = new UdpClient(endpoint);
            }
            else
            {
                this.Socket = new UdpClient(endpoint.AddressFamily);
            }
            this.Socket.Client.ReceiveBufferSize = 0x1000;
            this.Socket.Client.SendBufferSize = 0x1000;
            this.Socket.Ttl = 10;
            this.Socket.MulticastLoopback = false;
            this.Socket.Client.ReceiveBufferSize = 0xc350;
            if (((OSInfo.MajorVersion != 5) || (OSInfo.MinorVersion != 1)) && ((endpoint.Address != IPAddress.Any) && (endpoint.Address != IPAddress.IPv6Any)))
            {
                this.Socket.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
                this.Socket.Client.Bind(endpoint);
            }
        }

        public void Close()
        {
            Trace.WriteLine(string.Format("Socket {0} Close", this.GetHashCode()));
            Trace.Flush();
            if (this.Socket != null)
            {
                lock (this.Socket)
                {
                    this.Socket.Close();
                }
            }
        }

        public void Dispose()
        {
            Trace.WriteLine(string.Format("Socket {0} Dispose", this.GetHashCode()));
            Trace.Flush();
            this.Close();
        }

        public void JoinMulticastGroup(IPAddress group)
        {
            Trace.WriteLine(string.Format("Socket {0} JoinMulticastGroup", this.GetHashCode()));
            Trace.Flush();
            bool flag = group.AddressFamily == AddressFamily.InterNetworkV6;
            SocketOptionLevel optionLevel = flag ? SocketOptionLevel.IPv6 : SocketOptionLevel.IP;
            object optionValue = null;
            if (flag)
            {
                optionValue = new IPv6MulticastOption(group);
            }
            else
            {
                optionValue = new MulticastOption(group, this._endpoint.Address);
            }
            if (this.Socket != null)
            {
                lock (this.Socket)
                {
                    this.Socket.Client.SetSocketOption(optionLevel, SocketOptionName.AddMembership, optionValue);
                }
            }
        }

        public void Listen()
        {
            Trace.WriteLine(string.Format("Socket {0} Listen", this.GetHashCode()));
            Trace.Flush();
            if (this.Socket != null)
            {
                lock (this.Socket)
                {
                    this.Socket.BeginReceive(new AsyncCallback(this.ReceiveCallback), null);
                }
            }
        }

        private void ReceiveCallback(IAsyncResult result)
        {
            Trace.WriteLine(string.Format("Socket {0} ReceiveCallback", this.GetHashCode()));
            try
            {
                lock (this.Socket)
                {
                    if ((this.Socket != null) && (this.Socket.Client != null))
                    {
                        IPEndPoint remoteEP = null;
                        Trace.WriteLine(string.Format("{0} DiscoverySocket::ReceiveCallback entry point", DateTime.Now.ToString("MM/dd/yyyy HH:mm:ss:fff")));
                        byte[] bytes = this.Socket.EndReceive(result, ref remoteEP);
                        Trace.WriteLine(string.Format("{0} DiscoverySocket::ReceiveCallback from[{1}] message[{2}]", DateTime.Now.ToString("MM/dd/yyyy HH:mm:ss:fff"), remoteEP.ToString(), Encoding.UTF8.GetString(bytes)));
                        DiscoverySocketEventArgs e = new DiscoverySocketEventArgs {
                            Message = bytes,
                            Source = remoteEP
                        };
                        this.MessageReceived(this, e);
                        Trace.WriteLine(string.Format("{0} DiscoverySocket::ReceiveCallback begin receive", DateTime.Now.ToString("MM/dd/yyyy HH:mm:ss:fff")));
                        Trace.Flush();
                        this.Socket.BeginReceive(new AsyncCallback(this.ReceiveCallback), null);
                    }
                }
            }
            catch (Exception exception)
            {
                Trace.WriteLine(string.Format("{0} DiscoverySocket::ReceiveCallback error [{1}]", DateTime.Now.ToString("MM/dd/yyyy HH:mm:ss:fff"), exception.Message));
                Trace.Flush();
            }
        }

        public void Send(IPEndPoint destination, byte[] sendBytes)
        {
            Trace.WriteLine(string.Format("Socket {0} Send", this.GetHashCode()));
            Trace.Flush();
            if (this.Socket != null)
            {
                lock (this.Socket)
                {
                    this.Socket.Send(sendBytes, sendBytes.Length, destination);
                }
            }
        }

        public void Send(IPEndPoint destination, List<byte[]> sendBytes)
        {
            Trace.WriteLine(string.Format("Socket {0} Send", this.GetHashCode()));
            Trace.Flush();
            if (this.Socket != null)
            {
                lock (this.Socket)
                {
                    foreach (byte[] buffer in sendBytes)
                    {
                        this.Socket.Send(buffer, buffer.Length, destination);
                    }
                }
            }
        }

        public UdpClient Socket { get; protected set; }
    }
}

